// 匿名管道——实现进程池：父进程创建出n个子进程，由父进程写，子进程读并执行
#include "Task.hpp"
#include <iostream>
#include <unistd.h>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <string>

#define PROCESSNUM 10
#define NUM 1024

std::vector<task_t>tasks;

// 先描述
class channel
{
public:
    channel(int cmdfd, pid_t childid, std::string pro_name)
        : _cmdfd(cmdfd), _childid(childid), _pro_name(pro_name)
    {
    }

public:
    int _cmdfd;            // 发送任务的文件描述符
    pid_t _childid;        // 子进程的pid
    std::string _pro_name; // 子进程的名字 —— 方便打印日志
};
void Menu()
{
    std::cout << "################################################" << std::endl;
    std::cout << "#    1. 刷新日志          2. 更新回阳镜          #" << std::endl;
    std::cout << "#    3. 检测软件是否更新   4. 更新用的血量和蓝量   #" << std::endl;
    std::cout << "#                  0. 退出                      #" << std::endl;
    std::cout << "#################################################" << std::endl;
}
void test(const std::vector<channel> &channels)
{
    for (auto c : channels)
    {
        std::cout << c._cmdfd << " " << c._childid << " " << c._pro_name << std::endl;
    }
}

// 子进程读取
void Read()
{
    while (true)
    {
        int cmdcode = 0;
        int n = read(0, &cmdcode, sizeof(int)); // 使read每次只读取四个字节
        if (n == sizeof(int))
        {
            // 执行cmdcode对应的任务列表
            std::cout << "slayer say@ get a command: " << getpid() << " : cmdcode" << cmdcode << std::endl;
            if(cmdcode >= 0 && cmdcode < tasks.size())
                tasks[cmdcode]();
        }
        if(n == 0) break;
    }
}
// 父进程写入
void RecordChild(const int pipefd, const pid_t childid, std::vector<channel> &channels)
{
    static int i = 0;
    std::string name = "process-" + std::to_string(i);
    i++;
    channels.push_back(channel(pipefd, childid, name));
}
// 进程创建
void proCreate(std::vector<channel> &channels)
{
    // 确保每个子进程都只有一个写端
    std::vector<int> oldfds;
    for (int i = 0; i < PROCESSNUM; i++)
    {
        int pipefd[2] = {0};

        // 创建管道：默认pipefd[0]为读，pipefd[1]为写
        int n = pipe(pipefd);
        assert(!n);

        // 创建子进程
        pid_t id = fork();
        // 子进程
        if (id == 0)
        {
            // // 打印子进程从父进程继承的多余写端
            // std::cout << "child: " << getpid() << " close history fd: ";
            for(auto &fd:oldfds)
            {
                // std::cout << id << " pipe: " << fd << " ";
                close(fd);
            }
            // std::cout << std::endl;

            close(pipefd[1]);
            // dup2(oldfd,newfd):改变数据流向，将后者文件描述符对应的内容改为前者的文件描述符对应的内容
            dup2(pipefd[0], 0);
            close(pipefd[0]);   // 可关可不关
            // 读取数据
            Read();
            std::cout << "process: " << getpid() << " quit" << std::endl;
            exit(0);
        }
        // 父进程
        else if (id > 0)
        {
            close(pipefd[0]);

            // 父进程记录子进程
            RecordChild(pipefd[1], id, channels);
            oldfds.push_back(pipefd[1]);

            sleep(1);
        }
    }
}
// 进程控制:控制子进程的输出
void CtrlProcess(std::vector<channel> &channels)
{
    int which = 0;              // 用于轮询
    // int cnt = PROCESSNUM;                // 用于控制结束
    while(true)
    {
        // 输入要执行的任务
        int select = 0;
        Menu();
        std::cout << "Please Enter@ ";
        std::cin >> select;

        if(select <= 0 || select >= tasks.size()+1) break;

        // 1.选择任务

        // // 随机使用
        // int cmdcode = rand() % tasks.size();

        // 指定使用
        int cmdcode = select - 1;

        // 2.选择进程(随机访问)
        // int pro_pos = rand() % channels.size();

        std::cout << "father say: " 
                  << " cmdcode: " << cmdcode 
                  << " already sendto " << channels[which]._childid 
                  << " process name: " << channels[which]._pro_name
                  << std::endl;

        // 3.发送任务(向管道写入数据)
        write(channels[which]._cmdfd,&cmdcode,sizeof(cmdcode));

        which++;
        which %= channels.size();

        // cnt--;

        sleep(1);
    }
}
// 进程结束
void QuitProcess(const std::vector<channel> &channels)
{
    for(const auto& c:channels)
    {
        close(c._cmdfd);
        waitpid(c._childid,nullptr,0);
    }

    // // 关闭所有写端，那么读端会接收到0
    // for(const auto&c:channels)
    //     close(c._cmdfd);
    // sleep(5);

    // // 等待子进程结束
    // for(const auto&c:channels)
    //     waitpid(c._childid,nullptr,0);
    // sleep(5);
}

int main()
{
    LoadTask(&tasks);

    srand(time(nullptr) ^ getpid() ^ 1024); // 种一个随机数种子
    std::vector<channel> channels;

    // 1.进程创建
    proCreate(channels);
    // test(channels);

    // 2.进程控制
    CtrlProcess(channels);

    // 3.任务完成与进程回收
    QuitProcess(channels);

    // 睡眠结束后，继续执行，父进程便会结束
    // 如果程序接收到终止信号，父子进程都会结束
    // sleep(1000);

    return 0;
}